package P2PConnection;

import Commands.Command;
import Commands.InstructionListener;
import Commands.InstructionPack;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.locks.Lock;

/**
 * Created with IntelliJ IDEA.
 * Authors: Henrique Alberto Rusa and Danilo Barbosa Porto
 * Date: 01/12/13
 *
 * ConnectionHandler does the management of the instruction send and instruction receive. This will be the point where all the
 * data are send to it´s original interpreter.
 * The instructions will be put on an queue to be processed over time and requisitions to send instructions will be
 * considered when the watcher event, on the upper layer, gets a file system event, handling the job to the connection.
 */
public class ConnectionHandler implements Runnable{

    /*
     *  Parameter to implement the instruction evet for the instruction listeners, saving their reference on
     *  this list.
     */
    private List<InstructionListener> listeners;

    /*
     *  Parameter that saves the instructions received from the connections (FtpServer and SocketServer) to be processed
     *  when needed, and to not loose any object received.
     */
    private List<Command> instructionLine;

    /*
     *  Parameter SocketServer that keeps the reference of all incoming connections from object stream type. This
     *  will be kept alive, listening to the portNumber an creating a thread to talk to the client.
     */
    private SocketServer socketServer;

    /*
     *  Parameter FTPServer that keeps the reference of all incoming connections from file stream type. This
     *  will be kept alive, listening to the portNumber an creating a thread to talk to the client.
     */
    private FTPServer ftpServer;

    /*
     *  Parameter used to receive temporarily a instructionPack from the connection of the SocketServer.
     */
    private InstructionPack temp;

    /*
     *  Parameter that will be used to control the inner thread of the connection, the one that keeps verifying the
     *  instructionList to see if there is a newer received instruction.
     */
    private boolean controlInner = true;

    /*
     *  Parameter list that will keep all the references to the servers from all the filesystem (all the cluster servers
     *  used on this implementation).
     */
    private LinkedList<InetAddress> hosts;

    /*
     * Lock parameter to the instructionLine, implementing the synchronization to all the threads that access the
     * region
     */
    protected static Lock lock;

    /*
     *  Constructor of the class, where it initializes all the InetAddresses from the cluster to the list provided,
     *  allocating memory to the servers, our listener array and temporary instruction.
     */
    public ConnectionHandler(DownloadListener listen) {

        // rest of memory allocation: listener, queue of instructions, servers and temporary instruction
        temp = new InstructionPack();
        listeners = new ArrayList<InstructionListener>();
        instructionLine = Collections.synchronizedList(new LinkedList<Command>());
        socketServer = SocketServer.getInstance(instructionLine, lock);
        ftpServer = FTPServer.getInstance();
        ftpServer.addListener(listen);

        // Allocate the cluster references and add them to the list
        hosts = new LinkedList<InetAddress>();
        try {
            hosts.add(0, InetAddress.getByName("frontend"));
            hosts.add(1, InetAddress.getByName("node1"));
            hosts.add(2, InetAddress.getByName("node2"));
            hosts.add(3, InetAddress.getByName("node3"));
            hosts.add(4, InetAddress.getByName("node4"));
            hosts.add(5, InetAddress.getByName("node5"));
            hosts.add(6, InetAddress.getByName("node6"));
            hosts.add(7, InetAddress.getByName("node7"));
        } catch (UnknownHostException e) {
            //TODO
        }

    }

    /*
     *  Method to add a listener into the listeners list. It will have the listener warned when an event of listening
     *  occurs, getting the references from the listener list.
     */
    public void addListener(InstructionListener toAdd){
        listeners.add(toAdd);
    }

    /*
     *  Method to warn all the listeners of this class, iterating on the listeners list, and calling the method
     *  implemented by them from the listener interface.
     */
    public void newInstruction(Command instruction){
        for(InstructionListener element : listeners){
            element.hasNewInstruction(instruction);
        }
    }

    /*
     *  Method to initiate the application on the cluster, considering that the central node have to create the file
     *  system table, requesting all the nodes to update his table, merging them onto a unique one.
     */
    public void sendAllSynchronize(){

        // create update instruction
        InstructionPack sync = new InstructionPack();
        sync.makeUpdateTableInstruction(null);

        // iterate on the servers list to send them all the request.
        for(InetAddress element : hosts){
            try {
                new SocketClient(element).start(sync);
            } catch (IOException e) {
                //TODO
            }
        }
    }

    /*
     *  Method that sends a file. It creates a new clientSocket instance to receive the parameters and give the client
     *  the net address from the receiver.
     */
    public void sendFile(InetAddress host, String filename) throws IOException {
        (new FTPClient(host)).sendFile(filename);
    }

    /*
     *  Method that request a exchange of file. It creates a new clientSocket instance to receive the parameters and give the client
     *  the net address from the sender.
     */
    public void sendExchange(InetAddress host, String filename) throws IOException {
        InstructionPack exchange = new InstructionPack();
        exchange.makeExchangeInstruction(new File(filename));
        (new SocketClient(host)).start(exchange);
    }

    /*
     *  Method to update the connection table, requesting the central node to send his table, and them processing the
     *  request through the list of instructions.
     *  This call creates a new client to connect and request the table.
     */
    public void updateTable(Map<File,InetAddress> Table){

        // Create and make update instruction
        InstructionPack out = new InstructionPack();
        out.makeUpdateTableInstruction(Table);

        // creates client and sends the information to the central node
        try {
            new SocketClient(InetAddress.getByName("frontend")).start(out);
        } catch (UnknownHostException e) {
            //TODO
        } catch (IOException e) {
            //TODO
        }
    }

    /*
     *  Method run of the inner thread to verify the instruction list, and create an event if the list has any
     *  instruction to be processed.
     */
    @Override
    public void run() {

        // Initiates the servers of the class, and let them listen to their respective port Numbers
        socketServer.startServer();
        ftpServer.startServer();

        // Loop to verify the instruction list
        while(controlInner){

            // Add all the instructions received from the socket server to the list
            instructionLine.addAll(socketServer.returnInstructions());

            // if the list does have more instructions, call the event to the listeners
            if(!instructionLine.isEmpty()){
                temp = (InstructionPack) instructionLine.remove(0);
                this.newInstruction(temp);
            }
        }
    }

    /*
     *  Method to set dead or alive the inner thread of the class.
     */
    public void setAlive(boolean life){
        this.controlInner = life;
    }

    /*
     * Method to send a tuple into the central server table, so it creates a map and sends it within the package without
     * creating a new file on the host
     */
    public void sendCreateToCentral(File arq, InetAddress host){
        Map<File,InetAddress> map = Collections.synchronizedMap(new HashMap<File,InetAddress>());
        map.put(arq,host);
        InstructionPack create = new InstructionPack();
        create.makeCreateInstruction(arq,map);
        try {
            new SocketClient(host).start(create);
        } catch (IOException e) {
            //TODO
        }
    }

    /*
     *  Method to send a remove instruction to some node of the cluster, creating the package and sending it with the
     *  socket client.
     */
    public void sendRemove(File arq, InetAddress host){
        InstructionPack command = new InstructionPack();
        command.makeDeleteInstruction(arq);
        try {
            new SocketClient(host).start(arq);
        } catch (IOException e) {
            //TODO
        }
    }

    /*
     * Method to send a remove to the central server, so it will not remove a file from the filesystem, just remove the
     * reference from it table.
     */
    public void sendRemove(File arq){
        InstructionPack command = new InstructionPack();
        command.makeDeleteInstruction(arq);
        try {
            new SocketClient(InetAddress.getByName("frontend")).start(arq);
        } catch (UnknownHostException e) {
            //TODO
        } catch (IOException e) {
            //TODO
        }
    }

}
























